/*-------------------------------------------------------------------------
 *
 * qualify_function_stmt.c
 *	  Functions specialized in fully qualifying all function statements. These
 *	  functions are dispatched from qualify.c
 *
 *	  Fully qualifying function statements consists of adding the schema name
 *	  to the subject of the function and types as well as any other branch of
 *    the parsetree.
 *
 *	  Goal would be that the deparser functions for these statements can
 *	  serialize the statement without any external lookups.
 *
 * Copyright (c), Citus Data, Inc.
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/htup.h"
#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "parser/parse_func.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"

#include "distributed/deparser.h"
#include "distributed/version_compat.h"

using ObjectWithArgs = FuncWithArgs;
/* forward declaration for qualify functions */
static void QualifyFunction(ObjectWithArgs* func, ObjectType type);
static void QualifyFunctionSchemaName(ObjectWithArgs* func, ObjectType type);

/* AssertObjectTypeIsFunctionType asserts we aren't receiving something we shouldn't */
void AssertObjectTypeIsFunctional(ObjectType type)
{
    Assert(type == OBJECT_AGGREGATE || type == OBJECT_FUNCTION
#ifdef DISABLE_OG_COMMENTS
           || type == OBJECT_PROCEDURE || type == OBJECT_ROUTINE
#endif
    );
}

/*
 * QualifyAlterFunctionStmt transforms a
 * ALTER {AGGREGATE|FUNCTION|PROCEDURE} ..
 * statement in place and makes all (supported) statements fully qualified.
 *
 * Note that not all queries of this form are valid AlterFunctionStmt
 * (e.g. ALTER FUNCTION .. RENAME .. queries are RenameStmt )
 */
void QualifyAlterFunctionStmt(Node* node)
{
    AlterFunctionStmt* stmt = castNode(AlterFunctionStmt, node);
#ifdef DISABLE_OG_COMMENTS
    AssertObjectTypeIsFunctional(stmt->objtype);
#endif

    QualifyFunction(stmt->func, OBJECT_FUNCTION);
}

/*
 * QualifyRenameFunctionStmt transforms a
 * ALTER {AGGREGATE|FUNCTION|PROCEDURE} .. RENAME TO ..
 * statement in place and makes the function name fully qualified.
 */
void QualifyRenameFunctionStmt(Node* node)
{
    RenameStmt* stmt = castNode(RenameStmt, node);
    AssertObjectTypeIsFunctional(stmt->renameType);

    QualifyFunction(castNode(ObjectWithArgs, stmt->object), stmt->renameType);
}

/*
 * QualifyAlterFunctionSchemaStmt transforms a
 * ALTER {AGGREGATE|FUNCTION|PROCEDURE} .. SET SCHEMA ..
 * statement in place and makes the function name fully qualified.
 */
void QualifyAlterFunctionSchemaStmt(Node* node)
{
    AlterObjectSchemaStmt* stmt = castNode(AlterObjectSchemaStmt, node);
    AssertObjectTypeIsFunctional(stmt->objectType);

    QualifyFunction(castNode(ObjectWithArgs, stmt->object), stmt->objectType);
}

/*
 * QualifyAlterFunctionOwnerStmt transforms a
 * ALTER {AGGREGATE|FUNCTION|PROCEDURE} .. OWNER TO ..
 * statement in place and makes the function name fully qualified.
 */
void QualifyAlterFunctionOwnerStmt(Node* node)
{
    AlterOwnerStmt* stmt = castNode(AlterOwnerStmt, node);
#ifdef DISABLE_OG_COMMENTS
    AssertObjectTypeIsFunctional(stmt->objectType);
#endif

    QualifyFunction(castNode(ObjectWithArgs, stmt->object), OBJECT_FUNCTION);
}

/*
 * QualifyAlterFunctionDependsStmt transforms a
 * ALTER {FUNCTION|PROCEDURE} .. DEPENDS ON EXTENSION ..
 * statement in place and makes the function name fully qualified.
 */
void QualifyAlterFunctionDependsStmt(Node* node)
{
#ifdef DISABLE_OG_COMMENTS
    AlterObjectDependsStmt* stmt = castNode(AlterObjectDependsStmt, node);
    AssertObjectTypeIsFunctional(stmt->objectType);

    QualifyFunction(castNode(ObjectWithArgs, stmt->object), stmt->objectType);
#endif
    Assert(0);
    ereport(ERROR, (errmsg("Not supported in distributed openGauss currently")));
}

/*
 * QualifyFunction transforms a function in place and makes its name fully qualified.
 */
void QualifyFunction(ObjectWithArgs* func, ObjectType type)
{
    char* functionName = NULL;
    char* schemaName = NULL;

    /* check if the function name is already qualified */
    DeconstructQualifiedName(func->funcname, &schemaName, &functionName);

    /* do a lookup for the schema name if the statement does not include one */
    if (schemaName == NULL) {
        QualifyFunctionSchemaName(func, type);
    }
}

/*
 * QualifyFunction transforms a function in place using a catalog lookup for its schema
 * name to make it fully qualified.
 */
void QualifyFunctionSchemaName(ObjectWithArgs* func, ObjectType type)
{
    char* schemaName = NULL;
    char* functionName = NULL;

#ifdef DISABLE_OG_COMMENTS
    Oid funcid = LookupFuncWithArgs(type, func, true);
#endif
    Oid funcid = LookupFuncNameTypeNames(func->funcname, func->funcargs, true);

    HeapTuple proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));

    /*
     * We can not qualify the function if the catalogs do not have any records.
     *
     * e.g. DROP FUNC IF EXISTS non_existent_func() do not result in a valid heap tuple
     */
    if (HeapTupleIsValid(proctup)) {
        Form_pg_proc procform = (Form_pg_proc)GETSTRUCT(proctup);
        schemaName = get_namespace_name(procform->pronamespace);
        functionName = NameStr(procform->proname);
        functionName = pstrdup(functionName); /* we release the tuple before used */

        ReleaseSysCache(proctup);

        /* update the function using the schema name */
        func->funcname = list_make2(makeString(schemaName), makeString(functionName));
    }
}
